//----------------------------------------------------
// T7Game - Copyright (c) 湖南淘气网络科技有限公司
// Author:wulongfei
// Date:2017/6/6
// Description:
//----------------------------------------------------

using UnityEngine;
using System.Collections.Generic;
using System.Collections;
using System.IO;

namespace T7Game
{
    [XLua.LuaCallCSharp]
    public static class UResources
    {
        class AssetBundleInfo
        {
            private AssetBundle m_ab;
            private int m_useCount;
            public AssetBundleInfo(AssetBundle ab)
            {
                m_ab = ab;
                m_useCount = 0;
            }

            public AssetBundle GetAssetBundle()
            {
                return m_ab;
            }

            public int GetUseCount()
            {
                return m_useCount;
            }

            public void UseOnce()
            {
                m_useCount++;
            }

            public void UnUseOnce()
            {
                m_useCount--;
            }

        }

        class CallBackAssetInfo
        {
            public System.Action<UnityEngine.Object> callback;
            public string assetName;
            public CallBackAssetInfo(string name, System.Action<UnityEngine.Object> c)
            {
                assetName = name;
                callback = c;
            }
        }
        class LoadingAssetInfo
        {
            public LoadingAssetInfo(string assetPath)
            {
                name = assetPath;
                callbackAssetInfoList = new List<CallBackAssetInfo>();
            }

            public void Finish(AssetBundle ab)
            {
                for(int i = 0; i < callbackAssetInfoList.Count; ++i)
                {
                    CallBackAssetInfo info = callbackAssetInfoList[i];
                    if(ab != null)
                    {
                        info.callback(LoadObject(ab, info.assetName));
                    }
                    else
                    {
                        info.callback(new GameObject());
                    }
                }
            }

            public void AddCallBackAssetInfo(CallBackAssetInfo info)
            {
                callbackAssetInfoList.Add(info);
            }
            public string name;
            public List<CallBackAssetInfo> callbackAssetInfoList;
        }
        private const int kMaxAssetBundleCount = 50;
        private static Dictionary<string, AssetBundleInfo> s_assetBundleDic = new Dictionary<string, AssetBundleInfo>();
        private static List<string> s_delAssetBundlePathList = new List<string>();
        private static List<LoadingAssetInfo> s_loadingAssetBundleInfo = new List<LoadingAssetInfo>();
        private static AssetBundleManifest s_assetBundleMinifest = null;
        private static AssetBundle s_rootConfigAb = null;


        public static void Init()
        {
    #if !UNITY_EDITOR
            string minifestName = "StreamingAssets";
            string minifestPath = UPlatform.instance.GetDownloadingURL(minifestName, false);
            if(s_rootConfigAb != null)
            {
                s_rootConfigAb.Unload(true);
            }
            s_rootConfigAb = AssetBundle.LoadFromFile(minifestPath);
            s_assetBundleMinifest = s_rootConfigAb.LoadAsset<AssetBundleManifest>("assetbundlemanifest");
    #endif
        }

        private static bool IsTheAssetLoading(string name)
        {
            return GetLoadingAssetInfo(name) != null;
        }

        private static LoadingAssetInfo GetLoadingAssetInfo(string name)
        {
            int len = s_loadingAssetBundleInfo.Count;
            for(int i = 0; i < len; ++i)
            {
                LoadingAssetInfo info = s_loadingAssetBundleInfo[i];
                if (info.name.Equals(name))
                {
                    return info;
                }
            }

            return null;
        }

        private static void FinishLoading(string name, AssetBundle ab)
        {
            int len = s_loadingAssetBundleInfo.Count;
            for (int i = 0; i < len; ++i)
            {
                LoadingAssetInfo info = s_loadingAssetBundleInfo[i];
                if (info.name.Equals(name))
                {
                    info.Finish(ab);
                    s_loadingAssetBundleInfo.Remove(info);
                    break;
                }
            }
        }
        private static AssetBundleInfo LoadAssetBundle(string assetBundleName)
        {
            assetBundleName = assetBundleName.ToLower();
            AssetBundleInfo abInfo = null;
            LoadDependencies(assetBundleName);
            if (s_assetBundleDic.ContainsKey(assetBundleName))
            {
                abInfo = s_assetBundleDic[assetBundleName];
            }
            else
            {
                string downloadIngUrl = UPlatform.instance.GetDownloadingURL(assetBundleName);
                AssetBundle ab = AssetBundle.LoadFromFile(downloadIngUrl);
                if(ab != null)
                {
                    abInfo = new AssetBundleInfo(ab);
                    s_assetBundleDic.Add(assetBundleName, abInfo);
                }
            }

            if(abInfo != null)
            {
                abInfo.UseOnce();
            }

            return abInfo;
        }
        private static void LoadDependencies(string assetBundleName)
        {
            if(s_assetBundleMinifest == null)
            {
                return;
            }
            string[] dependencies = s_assetBundleMinifest.GetAllDependencies(assetBundleName);
            for(int i = 0; i < dependencies.Length; ++i)
            {
                string dependABName = dependencies[i];
                LoadAssetBundle(dependABName);
            }
        }
        private static UnityEngine.Object LoadObjectInOS(string name, System.Type t)
        {
            string assetBundleName = GetAssetPath(name);
            AssetBundleInfo abInfo = LoadAssetBundle(assetBundleName);
            UnityEngine.Object obj = null;
            if(abInfo != null)
            {
                obj = LoadObject(abInfo.GetAssetBundle(), name);
            }

            if (obj == null)
            {
                Debug.LogError("the file is not in asset bundle:" + name);
                obj = new GameObject();
            }

            return obj;
        }

        private static UnityEngine.Object LoadAssetBundleInEditor(string name, System.Type t)
        {
            UnityEngine.Object obj = null;
            string filePath = "/LTResources/" + name;
            string fullPath = Application.dataPath + filePath;
            fullPath = fullPath.Remove(fullPath.LastIndexOf('/'));
            if (Directory.Exists(fullPath))
            {
                DirectoryInfo info = new DirectoryInfo(fullPath);
                FileInfo[] fileInfoArr = info.GetFiles("*", SearchOption.TopDirectoryOnly);
                int fileInfoArrLen = fileInfoArr.Length;

                int index = name.LastIndexOf('/');
                if (index <= 0)
                {
                    index = name.LastIndexOf('\\');
                }

                string abPath = string.Empty;
                string fileName = name.Remove(0, index + 1);

                for (int i = 0; i < fileInfoArrLen; ++i)
                {
                    FileInfo fileInfo = fileInfoArr[i];
                    string fileFullName = fileInfo.FullName;
                    if (fileFullName.Contains(".meta"))
                    {
                        continue;
                    }

                    index = fileFullName.LastIndexOf('/');
                    if (index <= 0)
                    {
                        index = fileFullName.LastIndexOf('\\');
                    }
                    string otherName = fileFullName.Remove(0, index + 1);
                    otherName = otherName.Remove(otherName.LastIndexOf('.'));
                    if (otherName.CompareTo(fileName) == 0)
                    {
                        abPath = fileFullName.Remove(0, fileFullName.IndexOf("Assets"));
                        break;
                    }
                }

                if (!string.IsNullOrEmpty(abPath))
                {
    #if UNITY_EDITOR
                    obj = UnityEditor.AssetDatabase.LoadAssetAtPath(abPath, t);
    #endif
                }
            }

            if(obj == null)
            {
                Debug.LogError("load asset in LTResources failed:" + filePath);
            }

            return obj;
        }

        private static UnityEngine.Object LoadObject(AssetBundle bundle, string name)
        {
            UnityEngine.Object obj = null;

            if (bundle == null)
                return obj;

            string[] sps = name.Split('/');
            if (sps.Length > 0)
            {
                obj = bundle.LoadAsset(sps[sps.Length - 1]);
                if (obj != null)
                    return obj;
            }
            return obj;
        }

        private static string GetAssetPath(string assetFile)
        {
            string dir = assetFile;
            char lastChar = assetFile[assetFile.Length - 1];
            if (lastChar != '@')
            {
                dir = Path.GetDirectoryName(assetFile);
            }
            else
            {
                dir = assetFile.Remove(assetFile.Length - 1);
            }

            string assetName = dir.Remove(0, dir.LastIndexOf('/'));
            string rootDir = dir.Remove(dir.LastIndexOf('/'));

            return string.Concat(rootDir, assetName, ".asset");
        }


        public static T LoadFromAB<T>(string name) where T : UnityEngine.Object
        {
            UnityEngine.Object obj = null;
    #if UNITY_EDITOR
            obj = LoadAssetBundleInEditor(name, typeof(T));
    #else
            obj = LoadObjectInOS(name, typeof(T));
    #endif

            if (obj == null)
            {
                return null;
            }
            return (T)obj;
        }

        public static UnityEngine.Object LoadFromAB(string name, System.Type t)
        {
            UnityEngine.Object obj = null;
    #if UNITY_EDITOR
            obj = LoadAssetBundleInEditor(name, t);
#else
            obj = LoadObjectInOS(name, t);
#endif

            if (obj == null)
            {
                return null;
            }
            return obj;
        }

        public static T Load<T>(string name) where T : UnityEngine.Object
        {
            UnityEngine.Object obj = Resources.Load<T>(name);
            if (obj == null)
            {
                return null;
            }
            return (T)obj;
        }

        public static void LoadFromABASync(string name, System.Action<UnityEngine.Object> loadRet)
        {
    #if UNITY_EDITOR
            UnityEngine.Object obj = LoadAssetBundleInEditor(name, typeof(UnityEngine.Object));
            loadRet(obj);
    #else
            Client.instance.StartCoroutine(LoadABCoroutine(name, loadRet));
    #endif
        }

        private static IEnumerator LoadABCoroutine(string name, System.Action<UnityEngine.Object> loadRet)
        {
            string assetPath = GetAssetPath(name).ToLower();
            CallBackAssetInfo callbackInfo = new CallBackAssetInfo(name, loadRet);
            if (IsTheAssetLoading(assetPath))
            {
                GetLoadingAssetInfo(assetPath).AddCallBackAssetInfo(callbackInfo);
                yield break;
            }
            else
            {
                LoadingAssetInfo info = new LoadingAssetInfo(assetPath);
                info.AddCallBackAssetInfo(callbackInfo);
                s_loadingAssetBundleInfo.Add(info);
            }
            AssetBundleInfo abInfo = null;
            if (s_assetBundleDic.ContainsKey(assetPath))
            {
                abInfo = s_assetBundleDic[assetPath];
            }
            else
            {
                string assetFullPath = UPlatform.instance.GetDownloadingURL(assetPath);
                var bundleLoadRequest = AssetBundle.LoadFromFileAsync(assetFullPath);
                yield return bundleLoadRequest;
                if(bundleLoadRequest.assetBundle != null)
                {
                    abInfo = new AssetBundleInfo(bundleLoadRequest.assetBundle);
                    s_assetBundleDic.Add(assetPath, abInfo);
                    abInfo.UseOnce();
                }
            }

            if (abInfo == null)
            {
                FinishLoading(assetPath, null);
                Debug.LogError("Faild to load AssetBundle:" + name);
                yield break;
            }

            LoadDependencies(assetPath);
            var ab = abInfo.GetAssetBundle();
            FinishLoading(assetPath, ab);
        }

        public static void UnLoadAssetByName(string name, bool unLoad = false)
        {
            string assetPath = GetAssetPath(name);
            UnLoadAssetByPath(assetPath);
        }

        private static void UnLoadAssetByPath(string assetPath, bool unloadAllLoadedObjects = false)
        {
            assetPath = assetPath.ToLower();
            if (s_assetBundleDic.ContainsKey(assetPath))
            {
                AssetBundleInfo info = s_assetBundleDic[assetPath];
                if(info != null)
                {
                    var ab = info.GetAssetBundle();
                    ab.Unload(unloadAllLoadedObjects);
                }
                else
                {
                    Debug.LogError("the asset path:" + assetPath + " asset is null");
                }
                s_assetBundleDic.Remove(assetPath);
            }
        }

        public static void ClearAllAsset()
        {
            s_delAssetBundlePathList.Clear();
            foreach(var kvp in s_assetBundleDic)
            {
                s_delAssetBundlePathList.Add(kvp.Key);
            }

            int count = s_delAssetBundlePathList.Count;
            for(int i = 0; i < count; ++i)
            {
                UnLoadAssetByPath(s_delAssetBundlePathList[i]);
            }
        }
        public static void ClearUnUsedAsset()
        {
            int assetBundleCount = s_assetBundleDic.Count;
            if(assetBundleCount < kMaxAssetBundleCount)
            {
                return;
            }

            s_delAssetBundlePathList.Clear();

            int delCount = 0;
            foreach(var kvp in s_assetBundleDic)
            {
                int persistCount = assetBundleCount - delCount;
                if(persistCount < kMaxAssetBundleCount)
                {
                    break;
                }

                AssetBundleInfo info = kvp.Value;
                if(info.GetUseCount() <= 0)
                {
                    s_delAssetBundlePathList.Add(kvp.Key);
                    delCount++;
                }
                else
                {
                    info.UnUseOnce();
                }
            }

            int count = s_delAssetBundlePathList.Count;
            for(int i = 0; i < count; ++i)
            {
                UnLoadAssetByPath(s_delAssetBundlePathList[i]);
            }
        }

        public static T InstantiateFromAB<T>(string name) where T : UnityEngine.Object
        {
            UnityEngine.Object bObj = LoadFromAB<T>(name);
            if (bObj == null)
            {
                return null;
            }
            return (T)GameObject.Instantiate(bObj);
        }

        public static T InstantiateFromAB<T>(string name, Transform parent, bool worldPositionStays) where T : UnityEngine.Object
        {
            UnityEngine.Object bObj = LoadFromAB<T>(name);
            if (bObj == null)
            {
                return null;
            }
            return (T)GameObject.Instantiate(bObj, parent, worldPositionStays);
        }

        public static UnityEngine.Object InstantiateFromAB(string name, Transform parent, bool worldPositionStays, System.Type t)
        {
            UnityEngine.Object bObj = LoadFromAB(name, t);
            if (bObj == null)
            {
                return null;
            }
            return GameObject.Instantiate(bObj, parent, worldPositionStays);
        }

        public static T Instantiate<T>(string name) where T : UnityEngine.Object
        {
            var prefab = Resources.Load<T>(name);
            return (T)GameObject.Instantiate(prefab);
        }
    }
}